/**
 * ***************************************************************************** Copyright (c) 2006,
 * 2008 IBM Corporation and others. All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0 which accompanies this
 * distribution, and is available at http://www.eclipse.org/legal/epl-v10.html
 *
 * <p>Contributors: IBM Corporation - initial API and implementation
 * *****************************************************************************
 */
package org.eclipse.ltk.internal.core.refactoring.resource.undostates;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.net.URI;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFileState;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceStatus;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.ltk.internal.core.refactoring.RefactoringCoreMessages;

/**
 * {@link FileUndoState} is a lightweight description that describes a file to be created.
 *
 * <p>This class is not intended to be instantiated or used by clients.
 *
 * @since 3.4
 */
public class FileUndoState extends AbstractResourceUndoState {

  protected String name;

  private URI location;
  private String charset;
  private IFileContentDescription fileContentDescription;

  /**
   * Create a {@link FileUndoState} that can be used to later restore the given file. The file
   * typically already exists, but this constructor will not fail if the file does not exist.
   *
   * @param file the file to be restored.
   */
  public FileUndoState(IFile file) {
    super(file);
    this.name = file.getName();
    try {
      this.charset = file.getCharset(false);
    } catch (CoreException e) {
      // we don't care, a null charset is fine.
    }
    if (file.isLinked()) {
      location = file.getLocationURI();
    }
  }

  /**
   * Create a {@link FileUndoState} from the specified file handle. The handle does not exist, so no
   * information should be derived from it. If a location path is specified, this file should
   * represent a link to another location. The content description describes any state that should
   * be used when the file resource is created.
   *
   * @param file the file to be described
   * @param linkLocation the location of the file's link, or <code>null</code> if the file is not
   *     linked
   * @param fileContentDescription the file content description that can be used to get information
   *     about the file, such as its initial content
   */
  public FileUndoState(
      IFile file, URI linkLocation, IFileContentDescription fileContentDescription) {
    super(file);
    this.name = file.getName();
    this.location = linkLocation;
    this.charset = null;
    this.fileContentDescription = fileContentDescription;
  }

  public void recordStateFromHistory(IResource resource, IProgressMonitor monitor)
      throws CoreException {
    Assert.isLegal(resource.getType() == IResource.FILE);

    if (location != null) {
      // file is linked, no need to record any history
      return;
    }
    IFileState[] states = ((IFile) resource).getHistory(monitor);
    if (states.length > 0) {
      final IFileState state = getMatchingFileState(states);
      this.fileContentDescription =
          new IFileContentDescription() {
            /*
             * (non-Javadoc)
             *
             * @see org.eclipse.ui.internal.ide.undo.IFileContentDescription#exists()
             */
            public boolean exists() {
              return state.exists();
            }

            /*
             * (non-Javadoc)
             *
             * @see org.eclipse.ui.internal.ide.undo.IFileContentDescription#getContents()
             */
            public InputStream getContents() throws CoreException {
              return state.getContents();
            }

            /*
             * (non-Javadoc)
             *
             * @see org.eclipse.ui.internal.ide.undo.IFileContentDescription#getCharset()
             */
            public String getCharset() throws CoreException {
              return state.getCharset();
            }
          };
    }
  }

  public IResource createResourceHandle() {
    IWorkspaceRoot workspaceRoot = parent.getWorkspace().getRoot();
    IPath fullPath = parent.getFullPath().append(name);
    return workspaceRoot.getFile(fullPath);
  }

  public void createExistentResourceFromHandle(IResource resource, IProgressMonitor monitor)
      throws CoreException {

    Assert.isLegal(resource instanceof IFile);
    if (resource.exists()) {
      return;
    }
    IFile fileHandle = (IFile) resource;
    monitor.beginTask("", 200); // $NON-NLS-1$
    monitor.setTaskName(RefactoringCoreMessages.FileDescription_NewFileProgress);
    try {
      if (monitor.isCanceled()) {
        throw new OperationCanceledException();
      }
      if (location != null) {
        fileHandle.createLink(
            location, IResource.ALLOW_MISSING_LOCAL, new SubProgressMonitor(monitor, 200));
      } else {
        InputStream contents =
            new ByteArrayInputStream(
                RefactoringCoreMessages.FileDescription_ContentsCouldNotBeRestored.getBytes());
        // Retrieve the contents from the file content
        // description. Other file state attributes, such as timestamps,
        // have already been retrieved from the original IResource
        // object and are restored in #restoreResourceAttributes
        if (fileContentDescription != null && fileContentDescription.exists()) {
          contents = fileContentDescription.getContents();
        }
        fileHandle.create(contents, false, new SubProgressMonitor(monitor, 100));
        fileHandle.setCharset(charset, new SubProgressMonitor(monitor, 100));
      }
      if (monitor.isCanceled()) {
        throw new OperationCanceledException();
      }
    } catch (CoreException e) {
      if (e.getStatus().getCode() == IResourceStatus.PATH_OCCUPIED) {
        fileHandle.refreshLocal(IResource.DEPTH_ZERO, null);
      } else {
        throw e;
      }
    } finally {
      monitor.done();
    }
  }

  public boolean isValid() {
    if (location != null) {
      return super.isValid();
    }
    return super.isValid() && fileContentDescription != null && fileContentDescription.exists();
  }

  public String getName() {
    return name;
  }

  /**
   * Get the file state that matches this file description. The local time stamp is used to try to
   * find a matching file state. If none can be found, the most recent copy of the file state is
   * used.
   *
   * @param states file states
   * @return best guess state
   */
  private IFileState getMatchingFileState(IFileState[] states) {
    for (int i = 0; i < states.length; i++) {
      if (localTimeStamp == states[i].getModificationTime()) {
        return states[i];
      }
    }
    return states[0];
  }

  protected void restoreResourceAttributes(IResource resource) throws CoreException {
    super.restoreResourceAttributes(resource);
    Assert.isLegal(resource instanceof IFile);
    IFile file = (IFile) resource;
    if (charset != null) {
      file.setCharset(charset, null);
    }
  }
}
